{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tensorflow.python.keras.utils import get_file\n",
    "import gzip\n",
    "import numpy as np\n",
    "import keras\n",
    "from keras.preprocessing.image import ImageDataGenerator\n",
    "from keras.models import Sequential, Model\n",
    "from keras.layers import Dense, Dropout, Activation, Flatten\n",
    "from keras.layers import Conv2D, MaxPooling2D\n",
    "import os\n",
    "from keras import applications\n",
    "import cv2\n",
    "import functools\n",
    "from keras.models import load_model\n",
    "import pandas as pd\n",
    "from sklearn.preprocessing import MinMaxScaler\n",
    "# os.environ[\"CUDA_VISIBLE_DEVICES\"] = \"1\"  # 使用第2个GPU"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_data():\n",
    "    paths = [\n",
    "        'train-labels-idx1-ubyte.gz', 'train-images-idx3-ubyte.gz',\n",
    "        't10k-labels-idx1-ubyte.gz', 't10k-images-idx3-ubyte.gz'\n",
    "    ]\n",
    "\n",
    "    with gzip.open(paths[0], 'rb') as lbpath:\n",
    "        y_train = np.frombuffer(lbpath.read(), np.uint8, offset=8)\n",
    "\n",
    "    with gzip.open(paths[1], 'rb') as imgpath:\n",
    "        x_train = np.frombuffer(\n",
    "            imgpath.read(), np.uint8, offset=16).reshape(len(y_train), 28, 28, 1)\n",
    "\n",
    "    with gzip.open(paths[2], 'rb') as lbpath:\n",
    "        y_test = np.frombuffer(lbpath.read(), np.uint8, offset=8)\n",
    "\n",
    "    with gzip.open(paths[3], 'rb') as imgpath:\n",
    "        x_test = np.frombuffer(\n",
    "            imgpath.read(), np.uint8, offset=16).reshape(len(y_test), 28, 28, 1)\n",
    "\n",
    "    return (x_train, y_train), (x_test, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 读取数据与数据预处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "metadata": {},
   "outputs": [],
   "source": [
    " # read dataset\n",
    "(x_train, y_train), (x_test, y_test) = load_data()\n",
    "batch_size = 32\n",
    "epochs = 5\n",
    "data_augmentation = True  # 图像增强\n",
    "num_predictions = 20\n",
    "save_dir = os.path.join(os.getcwd(), 'saved_models_transfer_learning')\n",
    "model_name = 'keras_fashion_transfer_learning_trained_model.h5'\n",
    "\n",
    "\n",
    "# Convert class vectors to binary class matrices.  将类别弄成独热编码\n",
    "# y_train = keras.utils.to_categorical(y_train, num_classes)\n",
    "# y_test = keras.utils.to_categorical(y_test, num_classes)\n",
    "\n",
    "\n",
    "# x_train = x_train.astype('float32')\n",
    "# x_test = x_test.astype('float32')\n",
    "# 由于mist的输入数据维度是(num, 28, 28)，vgg16 需要三维图像,因为扩充一下mnist的最后一维\n",
    "X_train = [cv2.cvtColor(cv2.resize(i, (48, 48)), cv2.COLOR_GRAY2RGB) for i in x_train]\n",
    "X_test = [cv2.cvtColor(cv2.resize(i, (48, 48)), cv2.COLOR_GRAY2RGB) for i in x_test]\n",
    "\n",
    "x_train = np.asarray(X_train)\n",
    "x_test = np.asarray(X_test)\n",
    "\n",
    "x_train = x_train.astype('float32')\n",
    "x_test = x_test.astype('float32')\n",
    "\n",
    "x_train /= 255  # 归一化\n",
    "x_test /= 255  # 归一化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 伪造回归数据\n",
    "总共有10类衣服，他们每一类的价格设置规则如下：  \n",
    "以(45, 57, 85, 99, 125, 27, 180, 152, 225, 33)为每一类衣服的均值，以3为标准差，  \n",
    "利用正太分布小数点后两位的价格作为他们的衣服价格。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   label  price\n",
      "0      9  29.66\n",
      "1      0  45.15\n",
      "2      0  50.35\n",
      "3      3  99.19\n",
      "4      0  46.33\n",
      "-------------------\n",
      "   label   price\n",
      "0      9   32.51\n",
      "1      2   80.91\n",
      "2      1   56.77\n",
      "3      1   56.36\n",
      "4      6  181.70\n"
     ]
    }
   ],
   "source": [
    "# 转成DataFrame格式方便数据处理\n",
    "y_train_pd = pd.DataFrame(y_train)\n",
    "y_test_pd = pd.DataFrame(y_test)\n",
    "# 设置列名\n",
    "y_train_pd.columns = ['label'] \n",
    "y_test_pd.columns = ['label']\n",
    "\n",
    "# 给每一类衣服设置价格\n",
    "mean_value_list = [45, 57, 85, 99, 125, 27, 180, 152, 225, 33]  # 均值列表\n",
    "def setting_clothes_price(row):\n",
    "    price = sorted(np.random.normal(mean_value_list[int(row)], 3,size=1))[0] #均值mean,标准差std,数量\n",
    "    return np.round(price, 2)\n",
    "y_train_pd['price'] = y_train_pd['label'].apply(setting_clothes_price)\n",
    "y_test_pd['price'] = y_test_pd['label'].apply(setting_clothes_price)\n",
    "\n",
    "print(y_train_pd.head(5))\n",
    "print('-------------------')\n",
    "print(y_test_pd.head(5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据归一化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "60000\n",
      "10000\n"
     ]
    }
   ],
   "source": [
    "# y_train_price_pd = y_train_pd['price'].tolist()\n",
    "# y_test_price_pd = y_test_pd['price'].tolist()\n",
    "# 训练集归一化\n",
    "min_max_scaler = MinMaxScaler()\n",
    "min_max_scaler.fit(y_train_pd)\n",
    "y_train = min_max_scaler.transform(y_train_pd)[:, 1]\n",
    "\n",
    "# 验证集归一化\n",
    "min_max_scaler.fit(y_test_pd)\n",
    "y_test = min_max_scaler.transform(y_test_pd)[:, 1]\n",
    "y_test_label = min_max_scaler.transform(y_test_pd)[:, 0]  # 归一化后的标签\n",
    "print(len(y_train))\n",
    "print(len(y_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 迁移学习建模"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(48, 48, 3)\n",
      "Tensor(\"block5_pool_6/MaxPool:0\", shape=(?, 1, 1, 512), dtype=float32)\n"
     ]
    }
   ],
   "source": [
    "# 使用VGG16模型\n",
    "base_model = applications.VGG16(include_top=False, weights='imagenet', input_shape=x_train.shape[1:])  # 第一层需要指出图像的大小\n",
    "\n",
    "# # path to the model weights files.\n",
    "# top_model_weights_path = 'bottleneck_fc_model.h5'\n",
    "print(x_train.shape[1:])\n",
    "model = Sequential()\n",
    "print(base_model.output)\n",
    "model.add(Flatten(input_shape=base_model.output_shape[1:]))\n",
    "model.add(Dense(256, activation='relu'))\n",
    "model.add(Dropout(0.5))\n",
    "model.add(Dense(1))\n",
    "model.add(Activation('linear'))\n",
    "\n",
    "# add the model on top of the convolutional base\n",
    "model = Model(inputs=base_model.input, outputs=model(base_model.output))  # VGG16模型与自己构建的模型合并\n",
    "\n",
    "# 保持VGG16的前15层权值不变，即在训练过程中不训练  \n",
    "for layer in model.layers[:15]:\n",
    "    layer.trainable = False\n",
    "\n",
    "# initiate RMSprop optimizer\n",
    "opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)\n",
    "\n",
    "# Let's train the model using RMSprop\n",
    "model.compile(loss='mse',\n",
    "              optimizer=opt,\n",
    "              )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using real-time data augmentation.\n",
      "1875\n",
      "1875.0\n",
      "Epoch 1/5\n",
      "1875/1875 [==============================] - 1149s 613ms/step - loss: 0.0336 - val_loss: 0.0178\n",
      "Epoch 2/5\n",
      "1875/1875 [==============================] - 1805s 963ms/step - loss: 0.0183 - val_loss: 0.0146\n",
      "Epoch 3/5\n",
      "1875/1875 [==============================] - 2126s 1s/step - loss: 0.0162 - val_loss: 0.0140\n",
      "Epoch 4/5\n",
      "1875/1875 [==============================] - 2279s 1s/step - loss: 0.0151 - val_loss: 0.0145\n",
      "Epoch 5/5\n",
      "1875/1875 [==============================] - 2196s 1s/step - loss: 0.0144 - val_loss: 0.0136\n"
     ]
    }
   ],
   "source": [
    "if not data_augmentation:\n",
    "    print('Not using data augmentation.')\n",
    "    history = model.fit(x_train, y_train,\n",
    "              batch_size=batch_size,\n",
    "              epochs=epochs,\n",
    "              validation_data=(x_test, y_test),\n",
    "              shuffle=True)\n",
    "else:\n",
    "    print('Using real-time data augmentation.')\n",
    "    # This will do preprocessing and realtime data augmentation:\n",
    "    datagen = ImageDataGenerator(\n",
    "        featurewise_center=False,  # set input mean to 0 over the dataset\n",
    "        samplewise_center=False,  # set each sample mean to 0\n",
    "        featurewise_std_normalization=False,  # divide inputs by std of the dataset\n",
    "        samplewise_std_normalization=False,  # divide each input by its std\n",
    "        zca_whitening=False,  # apply ZCA whitening\n",
    "        zca_epsilon=1e-06,  # epsilon for ZCA whitening\n",
    "        rotation_range=0,  # randomly rotate images in the range (degrees, 0 to 180)\n",
    "        # randomly shift images horizontally (fraction of total width)\n",
    "        width_shift_range=0.1,\n",
    "        # randomly shift images vertically (fraction of total height)\n",
    "        height_shift_range=0.1,\n",
    "        shear_range=0.,  # set range for random shear\n",
    "        zoom_range=0.,  # set range for random zoom\n",
    "        channel_shift_range=0.,  # set range for random channel shifts\n",
    "        # set mode for filling points outside the input boundaries\n",
    "        fill_mode='nearest',\n",
    "        cval=0.,  # value used for fill_mode = \"constant\"\n",
    "        horizontal_flip=True,  # randomly flip images\n",
    "        vertical_flip=False,  # randomly flip images\n",
    "        # set rescaling factor (applied before any other transformation)\n",
    "        rescale=None,\n",
    "        # set function that will be applied on each input\n",
    "        preprocessing_function=None,\n",
    "        # image data format, either \"channels_first\" or \"channels_last\"\n",
    "        data_format=None,\n",
    "        # fraction of images reserved for validation (strictly between 0 and 1)\n",
    "        validation_split=0.0)\n",
    "\n",
    "    # Compute quantities required for feature-wise normalization\n",
    "    # (std, mean, and principal components if ZCA whitening is applied).\n",
    "    datagen.fit(x_train)\n",
    "    print(x_train.shape[0]//batch_size)  # 取整\n",
    "    print(x_train.shape[0]/batch_size)  # 保留小数\n",
    "    # Fit the model on the batches generated by datagen.flow().\n",
    "    history = model.fit_generator(datagen.flow(x_train, y_train,  # 按batch_size大小从x,y生成增强数据\n",
    "                                     batch_size=batch_size),  \n",
    "                        # flow_from_directory()从路径生成增强数据,和flow方法相比最大的优点在于不用\n",
    "                        # 一次将所有的数据读入内存当中,这样减小内存压，这样不会发生OOM\n",
    "                        epochs=epochs,\n",
    "                        steps_per_epoch=x_train.shape[0]//batch_size,\n",
    "                        validation_data=(x_test, y_test),\n",
    "                        workers=10  # 在使用基于进程的线程时，最多需要启动的进程数量。\n",
    "                       )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型可视化与保存模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "input_7 (InputLayer)         (None, 48, 48, 3)         0         \n",
      "_________________________________________________________________\n",
      "block1_conv1 (Conv2D)        (None, 48, 48, 64)        1792      \n",
      "_________________________________________________________________\n",
      "block1_conv2 (Conv2D)        (None, 48, 48, 64)        36928     \n",
      "_________________________________________________________________\n",
      "block1_pool (MaxPooling2D)   (None, 24, 24, 64)        0         \n",
      "_________________________________________________________________\n",
      "block2_conv1 (Conv2D)        (None, 24, 24, 128)       73856     \n",
      "_________________________________________________________________\n",
      "block2_conv2 (Conv2D)        (None, 24, 24, 128)       147584    \n",
      "_________________________________________________________________\n",
      "block2_pool (MaxPooling2D)   (None, 12, 12, 128)       0         \n",
      "_________________________________________________________________\n",
      "block3_conv1 (Conv2D)        (None, 12, 12, 256)       295168    \n",
      "_________________________________________________________________\n",
      "block3_conv2 (Conv2D)        (None, 12, 12, 256)       590080    \n",
      "_________________________________________________________________\n",
      "block3_conv3 (Conv2D)        (None, 12, 12, 256)       590080    \n",
      "_________________________________________________________________\n",
      "block3_pool (MaxPooling2D)   (None, 6, 6, 256)         0         \n",
      "_________________________________________________________________\n",
      "block4_conv1 (Conv2D)        (None, 6, 6, 512)         1180160   \n",
      "_________________________________________________________________\n",
      "block4_conv2 (Conv2D)        (None, 6, 6, 512)         2359808   \n",
      "_________________________________________________________________\n",
      "block4_conv3 (Conv2D)        (None, 6, 6, 512)         2359808   \n",
      "_________________________________________________________________\n",
      "block4_pool (MaxPooling2D)   (None, 3, 3, 512)         0         \n",
      "_________________________________________________________________\n",
      "block5_conv1 (Conv2D)        (None, 3, 3, 512)         2359808   \n",
      "_________________________________________________________________\n",
      "block5_conv2 (Conv2D)        (None, 3, 3, 512)         2359808   \n",
      "_________________________________________________________________\n",
      "block5_conv3 (Conv2D)        (None, 3, 3, 512)         2359808   \n",
      "_________________________________________________________________\n",
      "block5_pool (MaxPooling2D)   (None, 1, 1, 512)         0         \n",
      "_________________________________________________________________\n",
      "sequential_7 (Sequential)    (None, 1)                 131585    \n",
      "=================================================================\n",
      "Total params: 14,846,273\n",
      "Trainable params: 7,211,009\n",
      "Non-trainable params: 7,635,264\n",
      "_________________________________________________________________\n",
      "Saved trained model at /home/student/ChileWang/machine_learning_homework/question_one/saved_models_transfer_learning/keras_fashion_transfer_learning_trained_model.h5 \n"
     ]
    }
   ],
   "source": [
    "model.summary()\n",
    "# Save model and weights\n",
    "if not os.path.isdir(save_dir):\n",
    "    os.makedirs(save_dir)\n",
    "model_path = os.path.join(save_dir, model_name)\n",
    "model.save(model_path)\n",
    "print('Saved trained model at %s ' % model_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练过程可视化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "# 绘制训练 & 验证的损失值\n",
    "plt.plot(history.history['loss'])\n",
    "plt.plot(history.history['val_loss'])\n",
    "plt.title('Model loss')\n",
    "plt.ylabel('Loss')\n",
    "plt.xlabel('Epoch')\n",
    "plt.legend(['Train', 'Test'], loc='upper left')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型的预测功能"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[  9.          36.39809359]\n",
      " [  2.          90.78814453]\n",
      " [  1.          58.21489228]\n",
      " ...\n",
      " [  8.         225.04189952]\n",
      " [  1.          58.29625367]\n",
      " [  5.         118.09064091]]\n"
     ]
    }
   ],
   "source": [
    "# 预测\n",
    "y_new = model.predict(x_test)\n",
    "# 反归一化\n",
    "min_max_scaler.fit(y_test_pd)\n",
    "y_pred_pd = pd.DataFrame({'label':list(y_test_label), 'price':list(y_new)})\n",
    "y_new = min_max_scaler.inverse_transform(y_pred_pd)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 真实值与预测值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "    label   price  true_price\n",
      "0       9   32.51       32.51\n",
      "1       2   80.91       80.91\n",
      "2       1   56.77       56.77\n",
      "3       1   56.36       56.36\n",
      "4       6  181.70      181.70\n",
      "5       1   58.06       58.06\n",
      "6       4  127.31      127.31\n",
      "7       6  180.50      180.50\n",
      "8       5   27.21       27.21\n",
      "9       7  147.69      147.69\n",
      "10      4  123.40      123.40\n",
      "11      5   28.70       28.70\n",
      "12      7  153.20      153.20\n",
      "13      3   99.74       99.74\n",
      "14      4  117.12      117.12\n",
      "15      1   56.82       56.82\n",
      "16      2   84.18       84.18\n",
      "17      4  124.43      124.43\n",
      "18      8  221.80      221.80\n",
      "19      0   50.01       50.01\n"
     ]
    }
   ],
   "source": [
    "y_test_pd['true_price'] = pd.DataFrame(y_test_pd['price'])\n",
    "print(y_test_pd.head(20))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
